package org.activequant.util.log;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import javax.swing.text.NumberFormatter;

import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.account.Order;
import org.activequant.core.domainmodel.data.Quote;
import org.activequant.core.domainmodel.events.OrderCancelEvent;
import org.activequant.core.domainmodel.events.OrderCompletionEvent;
import org.activequant.core.domainmodel.events.OrderErrorEvent;
import org.activequant.core.domainmodel.events.OrderEvent;
import org.activequant.core.domainmodel.events.OrderExecutionEvent;
import org.activequant.core.domainmodel.events.OrderRejectEvent;
import org.activequant.core.domainmodel.events.OrderUpdateEvent;
import org.activequant.core.types.OrderSide;

/**
 * The CSV Logger logs business logic events to a file. In this implementation,
 * it starts a new log file and does not append to an existing file, in case the
 * filename provided in the constructor points to an existing file. <br>
 * <br>
 * The output file is a csv file, with at least three columns: <br>
 * - TimeStamp in nanoseconds<br>
 * - Instrument Specification ID<br>
 * - Log Event Type<br>
 * - Additional event specific payload<br>
 * <br>
 * CsvLogger extends LoggerBase.<br/>
 * Holds the following associated variables:
 * <ul>
 * <li>thePnlWriter(BufferedWriter)</li>
 * <li>theWriter(BufferedWriter)</li>
 * <li>theNumberFormat(NumberFormatter)</li>
 * <li>thePnl(double)</li>
 * </ul>
 * Holds the following inherited variables:
 * <ul>
 * <li>thePositions(HashMap&lt;Long, Double&gt;)</li>
 * <li>thePositionValue(HashMap&lt;Long, Double&gt;)</li>
 * <li>theInstrumentValuationPrices(HashMap&lt;Long, Double&gt;)</li>
 * </ul>
 * History<br>
 * - [5.10.2009] Created (Ulrich Staudinger)<br>
 * 
 * @author Ulrich Staudinger
 * 
 */
public class CsvLogger extends LoggerBase {
	/**
	 * constructs a CsvLogger(extends LoggerBase) using the given aCommonStream(OutputStream) and aPnlStream(OutputStream) to initialize its associated 
	 * theWriter(BufferedWriter) and thePnlWriter(BufferedWriter)
	 * @param aCommonStream
	 * @param aPnlStream
	 * @throws IOException
	 */
	public CsvLogger(OutputStream aCommonStream, OutputStream aPnlStream) throws IOException {
		theWriter = new BufferedWriter(new OutputStreamWriter(aCommonStream));
		thePnlWriter = new BufferedWriter(new OutputStreamWriter(aPnlStream));
	}
	/**
	 * loops the given aSpecs(InstrumentSpecification...) and for each InstrumentSpecification whose id(long) is not mapped as a key in the
	 * inherited thePositions(HashMap&lt;Long, Double&gt;) it adds an id - 0 mapping into the inherited thePositions(HashMap&lt;Long, Double&gt;)
	 * and theInstrumentValuationPrices(HashMap&lt;Long, Double&gt;)
	 */
	public void announceInstrumentSpecifications(InstrumentSpecification... aSpecs) {
		for (InstrumentSpecification mySpec : aSpecs) {
			long myIId = mySpec.getId();
			if (!thePositions.containsKey(myIId)) {
				thePositions.put(myIId, 0.0);
				theInstrumentValuationPrices.put(myIId, 0.0);
			}
		}
	}
	/**
	 * creates an appropriate String for the given anOrder(Order) and anEvent(OrderEvent) and writes it
	 * into the associated theWriter(BufferedWriter)
	 */
	public void log(Order anOrder, OrderEvent anEvent) {
		try {
			StringBuffer mySb = new StringBuffer();

			mySb.append("" + anEvent.getEventTimeStamp().getNanoseconds()+ ";");

			if (anEvent instanceof OrderCancelEvent) {
				append(mySb, anOrder, (OrderCancelEvent) anEvent);
			} else if (anEvent instanceof OrderCompletionEvent) {
				append(mySb, anOrder, (OrderCompletionEvent) anEvent);
			} else if (anEvent instanceof OrderErrorEvent) {
			} else if (anEvent instanceof OrderExecutionEvent) {
				append(mySb, anOrder, (OrderExecutionEvent) anEvent);
			} else if (anEvent instanceof OrderUpdateEvent) {
				append(mySb, anOrder, (OrderUpdateEvent) anEvent);
			} else if (anEvent instanceof OrderRejectEvent) {

			}
			String myInstrumentId = "";
			theWriter.write(myInstrumentId);

		} catch (IOException e) {
			exception(e);
		}
	}
	/**
	 * appends the following String to the given anSb(StringBuffer):<br/>
	 * "${anOrder.instrumentSpecification.id};OrderSubmit;${anOrder.orderSide.name};${anOrder.orderType.name};${anOrder.limitPrice};${anOrder.limitPrice};\n"
	 * @param anSb
	 * @param anOrder
	 */
	private void append(StringBuffer anSb, Order anOrder) {
		anSb.append("" + anOrder.getInstrumentSpecification().getId());
		anSb.append(";OrderSubmit;");
		anSb.append(anOrder.getOrderSide().name() + ";");
		anSb.append(anOrder.getOrderType().name() + ";");
		anSb.append(anOrder.getLimitPrice() + ";");
		anSb.append(anOrder.getLimitPrice() + ";");
		anSb.append("\n");
	}
	/**
	 * appends the following String to the given anSb(StringBuffer):<br/>
	 * "${anOrder.instrumentSpecification.id};OrderCancel;${anEvent.message}\n"
	 * @param anSb
	 * @param anOrder
	 * @param anEvent
	 */
	private void append(StringBuffer anSb, Order anOrder, OrderCancelEvent anEvent) {
		anSb.append("" + anOrder.getInstrumentSpecification().getId());
		anSb.append(";OrderCancel;");
		anSb.append(anEvent.getMessage());
		anSb.append("\n");
	}
	/**
	 * appends the following String to the given anSb(StringBuffer):<br/>
	 * "${anOrder.instrumentSpecification.id};OrderCompletion;${anEvent.averagePrice};${anEvent.totalQuantity};${anEvent.totalCommission};\n"
	 * @param anSb
	 * @param anOrder
	 * @param anEvent
	 */
	private void append(StringBuffer anSb, Order anOrder, OrderCompletionEvent anEvent) {
		anSb.append("" + anOrder.getInstrumentSpecification().getId());
		anSb.append(";OrderCompletion;");
		anSb.append(anEvent.getAveragePrice() + ";");
		anSb.append(anEvent.getTotalQuantity() + ";");
		anSb.append(anEvent.getTotalCommission() + ";");
		anSb.append("\n");
	}
	/**
	 * <strong>1.</strong> Appends the following String to the given anSb(StringBuffer) and then writes that to the associated theWriter(BufferedWriter):<br/>
	 * "${anOrder.instrumentSpecification.id};OrderExecution;${anEvent.price};${anEvent.quantity};${anEvent.commission};\n"<br/>
	 * <strong>2.</strong> It also updates the inherited thePositions(HashMap&lt;Long, Double&gt;) and theInstrumentValuationPrices(HashMap&lt;Long, Double&gt;) appropriately
	 * @param anSb
	 * @param anOrder
	 * @param anEvent
	 */
	private void append(StringBuffer anSb, Order anOrder, OrderExecutionEvent anEvent) {
		long myIId = anOrder.getInstrumentSpecification().getId();
		anSb.append("" + myIId);
		anSb.append(";OrderExecution;");
		anSb.append(anEvent.getPrice() + ";");
		anSb.append(anEvent.getQuantity() + ";");
		//anSb.append(anEvent.getCommission() + ";");
		anSb.append("\n");

		// as it is an order execution, we also update the running positions and
		// position values.
		if (!thePositions.containsKey(myIId)) {
			thePositions.put(myIId, 0.0);
			theInstrumentValuationPrices.put(myIId, anEvent.getPrice());
		}
		// update the position count.
		double myCurrentPosition = thePositions.get(myIId);
		// update the last price as well as the current position
		if (anOrder.getOrderSide().equals(OrderSide.BUY)) {
			thePositions.put(myIId, myCurrentPosition + anEvent.getQuantity());
		} else {
			thePositions.put(myIId, myCurrentPosition - Math.abs(anEvent.getQuantity()));
		}
		try {
			theWriter.write(anSb.toString() + "\n");
			theWriter.flush();
		} catch (IOException e) {
			exception(e);
		}
	}
	/**
	 * appends the following String to the given anSb(StringBuffer):<br/>
	 * "${anOrder.instrumentSpecification.id};OrderUpdate;${anEvent.updatedOrder};\n"
	 * @param anSb
	 * @param anOrder
	 * @param anEvent
	 */
	private void append(StringBuffer anSb, Order anOrder, OrderUpdateEvent anEvent) {
		anSb.append("" + anOrder.getInstrumentSpecification().getId());
		anSb.append(";OrderUpdate;");
		anSb.append(anEvent.getUpdatedOrder() + ";");
		anSb.append("\n");
	}
	/**
	 * invokes <code>anException.printStackTrace()</code> on the given anException(Exception)
	 * @param anException
	 */
	public void exception(Exception anException) {
		anException.printStackTrace();
	}

	/**
	 * the logging of quotes reevaluates the current positions. It is highly
	 * recommended that quotes are immediately sent out (by the log using class)
	 * after executions happen.<br/>
	 * Based on the given aQuote(Quote) it updates the last relevant price for the position into the associated theInstrumentValuationPrices(HashMap&lt;Long, Double&gt;)
	 * , updates the associated thePnl(double) and writes it into the associated  thePnlWriter(BufferedWriter) along with the timeStamp of the given aQuote(Quote)
	 * 
	 */
	public void log(Quote aQuote) {
		long myIId = aQuote.getInstrumentSpecification().getId();

		// update the position count.
		double myCurrentPosition = thePositions.get(myIId);
		double myLastPrice = theInstrumentValuationPrices.get(myIId);
		double myRelevantQuotePrice = myCurrentPosition > 0 ? aQuote.getBidPrice() : aQuote.getAskPrice();
		double myChangeInPrice = myCurrentPosition > 0 ? (aQuote.getBidPrice() - myLastPrice) : (myLastPrice - aQuote.getAskPrice());
		//
		double myChangeInPnl = myChangeInPrice * myCurrentPosition;
		thePnl += (myChangeInPnl / aQuote.getInstrumentSpecification().getTickSize()) * aQuote.getInstrumentSpecification().getTickValue();
		// track the current valuation price.
		theInstrumentValuationPrices.put(myIId, myRelevantQuotePrice);
		// 
		try {
			thePnlWriter.append(aQuote.getTimeStamp().getNanoseconds() + ";;PNL;" + theNumberFormat.valueToString(thePnl) + "\n");
			thePnlWriter.flush();
		} catch (Exception e) {
			exception(e);
		}

	}
	/**
	 * private NumberFormatter theNumberFormat = new NumberFormatter();
	 */
	private NumberFormatter theNumberFormat = new NumberFormatter();
	/**
	 * private BufferedWriter theWriter;
	 */
	private BufferedWriter theWriter;
	/**
	 * private BufferedWriter thePnlWriter;
	 */
	private BufferedWriter thePnlWriter;
	/**
	 * private double thePnl = 0.0;
	 */
	private double thePnl = 0.0;
}
